package com.nulstudio.cloud.common.entity;

import com.nulstudio.cloud.common.exception.NulException;
import com.nulstudio.cloud.common.exception.NulExceptionConstants;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;

import java.util.*;

/**
 * Role of a user, represents a set of authorities in database.
 * @author nullsty
 * @since 2.0
 */
public final class NulRole {

    /**
     * Normal role status.
     */
    public static final int ROLE_STATUS_NORMAL = 0;

    /**
     * Blocked role status. Users with this role have no authorities.
     */
    public static final int ROLE_STATUS_BLOCKED = 1;

    /**
     * Prefix of roles in Spring Security.
     */
    private static final String ROLE_PREFIX = "ROLE_";

    /**
     * ID of the role.
     */
    private int id;

    /**
     * Name of the role, must be unique.
     */
    private String name;

    /**
     * Description of the role.
     */
    private String description;

    /**
     * Creation time of the role.
     */
    private Date creationTime;

    /**
     * Last update time of the role.
     */
    private Date updateTime;

    /**
     * UID of the user who created the role.
     */
    private int creationUid;

    /**
     * UID of the user who last updated the role.
     */
    private int updateUid;

    /**
     * Status the role, such as Blocked, etc.
     */
    private int status;

    /**
     * Authority set the role represents.
     */
    private Set<NulAuthority> authorities;

    /**
     * Create an empty {@code NulRole} with fields initialized with null values.
     */
    public NulRole() {
        this.id = 0;
        this.name = null;
        this.description = null;
        this.creationTime = null;
        this.updateTime = null;
        this.creationUid = 0;
        this.updateUid = 0;
        this.status = ROLE_STATUS_NORMAL;
        this.authorities = null;
    }

    /**
     * Create a full initialized {@code NulRole} object.
     * @param id ID of the role
     * @param name name of the role, must be unique
     * @param description description of the role
     * @param creationTime creation time of the role
     * @param updateTime last update time of the role
     * @param creationUid UID of the user who created the role
     * @param updateUid UID of the user who last updated the role
     * @param status status of the role
     * @param authorities authorities the role represents
     */
    public NulRole(int id, String name, String description, @NonNull Date creationTime,
                   @NonNull Date updateTime, int creationUid,
                   int updateUid, int status, List<NulAuthority> authorities) {
        this.id = id;
        this.name = name;
        this.description = description;
        this.creationTime = new Date(creationTime.getTime());
        this.updateTime = new Date(updateTime.getTime());
        this.creationUid = creationUid;
        this.updateUid = updateUid;
        this.status = status;
        this.authorities = new HashSet<>(authorities);
    }

    /**
     * Get a list of {@code GrantedAuthority} for Spring Security.
     * @return a list of {@code GrantedAuthority} the role represents, including the role itself
     */
    @NonNull
    public Collection<GrantedAuthority> toSpringAuthorityList() {
        final List<GrantedAuthority> result = new ArrayList<>();
        if (name != null) {
            result.add(new SimpleGrantedAuthority(ROLE_PREFIX + name));
        }
        if (authorities != null) {
            result.addAll(authorities);
        }
        return result;
    }

    @NonNull
    public static Collection<GrantedAuthority> toSpringAuthorityList(
            @NonNull String role, @NonNull Collection<String> authorities
    ) {
        final List<GrantedAuthority> result = new ArrayList<>();
        result.add(new SimpleGrantedAuthority(ROLE_PREFIX + role));
        for (final String authority : authorities) {
            result.add(new SimpleGrantedAuthority(authority));
        }
        return result;
    }

    /**
     * Check validity for the specified object.
     * @param role object to verify
     * @throws NulException when the object is null, blocked, etc.
     */
    public static void assertValidity(@Nullable NulRole role) throws NulException {
        if (role == null)
            throw new NulException(NulExceptionConstants.ROLE_NOT_EXIST);
        if (role.getStatus() == ROLE_STATUS_BLOCKED)
            throw new NulException(NulExceptionConstants.ROLE_BLOCKED);
    }

    /**
     * Get id of the role.
     * @return id of the role
     */
    public int getId() {
        return id;
    }

    /**
     * Get name of the role.
     * @return name of the role
     */
    public String getName() {
        return name;
    }

    /**
     * Get description of the role.
     * @return description of the role
     */
    public String getDescription() {
        return description;
    }

    /**
     * Get creation time of the role.
     * @return creation time of the role
     */
    public Date getCreationTime() {
        return creationTime;
    }

    /**
     * Get last update time of the role.
     * @return last update time of the role
     */
    public Date getUpdateTime() {
        return updateTime;
    }

    /**
     * Get UID of the user who created the role.
     * @return UID of the user who created the role
     */
    public int getCreationUid() {
        return creationUid;
    }

    /**
     * Get UID of the user who last updated the role.
     * @return UID of the user who last updated the role
     */
    public int getUpdateUid() {
        return updateUid;
    }

    /**
     * Get status of the role.
     * @return status of the role
     */
    public int getStatus() {
        return status;
    }

    /**
     * Get set of {@code NulAuthority} the role represents.
     * @return a set of {@code NulAuthority} the role represents
     */
    public Set<NulAuthority> getAuthorities() {
        return authorities;
    }

    private void setId(int id) {
        this.id = id;
    }

    private void setName(String name) {
        this.name = name;
    }

    private void setDescription(String description) {
        this.description = description;
    }

    private void setCreationTime(Date creationTime) {
        this.creationTime = creationTime;
    }

    private void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }

    private void setCreationUid(int creationUid) {
        this.creationUid = creationUid;
    }

    private void setUpdateUid(int updateUid) {
        this.updateUid = updateUid;
    }

    private void setStatus(int status) {
        this.status = status;
    }

    private void setAuthorities(Set<NulAuthority> authorities) {
        this.authorities = authorities;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        final NulRole nulRole = (NulRole) o;
        return Objects.equals(name, nulRole.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name);
    }
}
